package com.atguigu.tingshu.common.aspect;


import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.conn.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
@Slf4j
public class GuiGuCacheAspect {


    //使用分布式锁解决缓存击穿问题
    //自定义切面类



    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;


    @Around("@annotation(guiGuCache)")
    public Object around(ProceedingJoinPoint point, GuiGuCache guiGuCache) {

        //获取实际的el表达式
        String cacheKeyParseException = guiGuCache.cacheKey();
        //调用解析方法
        String cacheKey = parseExpression(cacheKeyParseException, point, String.class);



        //获取目标方法对象的返回值类型,先获取目标方法的对象,然后再调用目标方法对象的方法获取目标方法的返回值类型
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //不单单获取到你的集合类型,还获取到集合中泛型的类型
        Type genericReturnType = method.getGenericReturnType();

        //1.先从redis中查询专辑数据
        String json = redisTemplate.opsForValue().get(cacheKey);
        if (!StringUtils.isEmpty(json)) {
            return JSON.parseObject(json, genericReturnType);
        }

        boolean enableLock = guiGuCache.enableLock();
        String lockNameException = guiGuCache.lockName();
        //如果要使用分布式锁
        if (enableLock && !lockNameException.isEmpty()){
            //el表达式解析器解析
            String lockName = parseExpression(lockNameException, point, String.class);
            //如果redis中没有获取到数据,那么就使用分布式锁来解决缓存击穿的问题
            //获取分布式锁
            RLock lock = redissonClient.getLock(lockName);
            boolean tryLock = lock.tryLock();
            if (tryLock) {
                log.info("线程{}获取到了锁", Thread.currentThread().getName());
                //从数据库中查询获取专辑数据
                try {
                    Object result = point.proceed();

                    //然后存储到redis中
                    redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result),
                            RedisConstant.ALBUM_TIMEOUT, TimeUnit.SECONDS);

                    return result;
                } catch (Throwable throwable) {
                    throw new RuntimeException(throwable.getMessage());
                } finally {
                    lock.unlock();
                    log.info("线程{}释放了锁", Thread.currentThread().getName());
                }
            } else {
                log.info("线程{}没有获取到锁,进行自旋", Thread.currentThread().getName());
                return around(point, guiGuCache);

            }
        }else {
            //如果不使用分布式锁,那么直接从数据库中查询
            try {
                Object result = point.proceed();

                //然后存储到redis中
                redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(result),
                        RedisConstant.ALBUM_TIMEOUT, TimeUnit.SECONDS);

                return result;
            }catch (Throwable throwable) {
                throw new RuntimeException(throwable.getMessage());
            }


        }


    }


    //el表达式解析器进行解析
    //创建一个解析器
    private static final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();

    private <T> T parseExpression(String cacheKeyParseException, ProceedingJoinPoint point, Class<T> stringClass) {
        //解析实际传过来的el表达式,只解析#{}中的数据
        Expression expression = spelExpressionParser.parseExpression(cacheKeyParseException, ParserContext.TEMPLATE_EXPRESSION);
        // 将调用目标方法的参数预设给表达式解析器对象
        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext();
        standardEvaluationContext.setVariable("params", point.getArgs());
        T value = expression.getValue(standardEvaluationContext, stringClass);
        return value;
    }
}
